feat: structured logging for provider diagnostics + Cursor SDK rules/skills logs - #85
Merged
Conversation
…skills logs
Converts every console.warn/console.error in the provider (agent-backend,
agent-events, language-model) to route through opencode's client.app.log()
API instead, with a console.* fallback when no client bridge is published.
Also captures @cursor/sdk's own internal rules/skills load-completion
diagnostics (e.g. "LocalCursorRulesService load completed
meta={durationMs, ruleCount}"), which the SDK writes straight to
console.log with no public logger hook, and re-emits them as structured
opencode logs instead of raw terminal noise:
- In-process transport: a narrowly-scoped console.log interceptor matches
only the three known Cursor rules/skills messages; everything else
passes through unchanged (src/provider/cursor-log-intercept.ts).
- Sidecar transport: the child process installs the same interception and
forwards matches over the existing JSONL protocol as a new "log" event
(src/sidecar/agent-host.mjs), which SidecarClient forwards via a new
onLog option.
New src/provider/log-bridge.ts mirrors the existing subagent-bridge.ts
globalThis pattern to give the provider layer access to the plugin's
opencode client without a circular import.
justin-carper
approved these changes
Jul 30, 2026
justin-carper
added a commit
that referenced
this pull request
Aug 3, 2026
Two conflicts, both resolved to keep each side's intent: - language-model.ts: #85/#86 moved the resolveSystemDelivery call inside the withSessionLock callback. The skills-catalogue lookup was re-placed into that relocated call rather than restored to its old position. - CHANGELOG.md: the skills-bridge entry stays under [Unreleased]; main's 0.6.2 and 0.7.0 release sections are kept in full.
justin-carper
added a commit
that referenced
this pull request
Aug 3, 2026
0.7.0 (#85) moved the plugin's own diagnostics off console.* and onto opencode's structured logging API. The skills bridge predated that and still wrote to the terminal, leaving the only console.* calls in src/. Withheld skills and the mirror-failure path now pass structured extra fields instead of pre-formatted strings, so the reason codes are queryable rather than embedded in a message. Also corrects the plugin-cache path in the README limitation: it is platform-dependent, as PLUGIN_CACHE_PATH shows, and was documented as POSIX-only.
justin-carper
added a commit
that referenced
this pull request
Aug 3, 2026
…scovery (#90) * feat: mirror opencode skills into .cursor/skills/ for Cursor agent discovery Discover opencode's resolved skills (project + global + config.skills.paths), filter through permission config, and materialise them as a git-ignored mirror in <cwd>/.cursor/skills/ with a 'generated: opencode-cursor' sentinel. An <available_skills> catalogue is appended to the generated system rule so the Cursor agent can discover and load skills on demand. - New: src/plugin/skill-discovery.ts — filesystem walk, frontmatter parsing, permission filtering (map-form + rule-array), extraPaths, path expansion, skillSetHash including all files (mtime + size). - New: src/provider/skill-mirror.ts — materialisation with sentinel, git-ignore, idempotent writes, stale-dir pruning, user-owned protection, per-file 1MB skip, total 10MB cap, removeSkillMirror for dispose, buildSkillsCatalogue. - Updated: src/provider/system-rule.ts — writeSystemRule and resolveSystemDelivery accept optional skillsCatalogue appended to rule body. - Updated: src/plugin/index.ts — forwardSkills option (default true), skills.include/exclude override, materialisation in config hook, live re-sync in chat.params hook (hash-gated), currentSkillsCatalogue always forwarded per turn, removeSkillMirror in dispose. - Updated: src/provider/language-model.ts — dynamic + static catalogue resolution (no self-provisioning — respects forwardSkills:false). - Updated: src/provider/delegate.ts + src/plugin/cursor-tools.ts — settingSources: ['project'] passed to delegate's acquireAgent. - Updated: src/provider/index.ts — skillsCatalogue in provider options. - Tests: 403 passing (25 skill-discovery, 14 skill-mirror, 10 plugin-skill- mirror, 6 catalogue in language-model-system, plus existing). - Docs: README.md (Skills section), CHANGELOG.md ([Unreleased]), SECURITY.md (skills mirror threat model). - .gitignore: exclude .cursor/ (plugin-generated runtime artifacts). * fix(skills): follow symlinked skill dirs and files Dirent.isDirectory() is false for a symlink pointing at a directory, so a skill linked in from a shared checkout was dropped from the mirror with no warning. Symlinked supporting files were lost the same way. Entries are now classified through the link target, with a resolved-path visited set guarding the cycles that following links admits. * docs(skills): correct deny scope, note plugin-skill gap skills.include is checked before permissions resolve, so it re-admits a denied skill by design — SECURITY.md read as though deny were absolute. Skills bundled in opencode plugin packages live in the package cache, which is not a scanned location, and the SDK exposes no skills API to resolve them. Documented so the omission is visible rather than silent. * style(plugin): normalize indentation in skills bridge blocks * refactor(skills): route mirror diagnostics through pluginLog 0.7.0 (#85) moved the plugin's own diagnostics off console.* and onto opencode's structured logging API. The skills bridge predated that and still wrote to the terminal, leaving the only console.* calls in src/. Withheld skills and the mirror-failure path now pass structured extra fields instead of pre-formatted strings, so the reason codes are queryable rather than embedded in a message. Also corrects the plugin-cache path in the README limitation: it is platform-dependent, as PLUGIN_CACHE_PATH shows, and was documented as POSIX-only. --------- Co-authored-by: Wayne Simpson <wayne@nocodecreative.io>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The plugin's own diagnostics, and diagnostics from the bundled
@cursor/sdk, were being written straight toconsole.warn/console.error/console.loginstead of using opencode's plugin logging API. This surfaced as raw, unstructured lines in the terminal, e.g.:These two (plus a third,
CursorPluginsAgentSkillsService load completed) come from@cursor/sdk's bundled local-exec runtime, which writes them straight toconsole.log— there's no public logger hook in the SDK to redirect them.What changed
console.warn/console.errorcall inagent-backend.ts,agent-events.ts, andlanguage-model.tsnow routes throughclient.app.log({ body: { service: "opencode-cursor", level, message, extra } }), with aconsole.*fallback when no client bridge is available (e.g. the provider used standalone/in tests).src/provider/log-bridge.ts: mirrors the existingsubagent-bridge.tsglobalThispattern so the provider layer (which has no direct import path to the plugin's opencode client) can log through it. Published byplugin/index.tson init, cleared ondispose().src/provider/cursor-log-intercept.ts: a narrowly-scopedconsole.loginterceptor used for the in-process transport. It matches only the three known Cursor rules/skills "load completed" messages (ANSI-safe) and re-emits them viapluginLog; every otherconsole.logcall passes through untouched.src/sidecar/agent-host.mjs(the Node child that hosts@cursor/sdkwhen Bun'shttp2client can't be used) installs the same interception in the child process and forwards matches to the parent over the existing JSONL protocol as a new{ ev: "log" }message.SidecarClientgained anonLogoption to receive these.Testing
npm run typecheck— cleannpm test— 371 passed (29 pre-existing files + 2 new:log-bridge.test.ts,cursor-log-intercept.test.ts;sidecar.test.tsgained a log-forwarding case backed by a newemitRulesLogflag in the fake SDK fixture)npm run build— verified the interceptor + log bridge are present in bothdist/chunk-*.js(shared by provider/plugin) anddist/sidecar/agent-host.jsNotes
A broader interception of every Cursor SDK
console.logcall would risk swallowing unrelated output; this only recognizes the three confirmed rules/skills messages. If Cursor exposes a proper logger callback upstream, we can drop the interceptor and pass it through directly.